/*
 *	Firebird ADO.NET Data provider for .NET and Mono 
 * 
 *	   The contents of this file are subject to the Initial 
 *	   Developer's Public License Version 1.0 (the "License"); 
 *	   you may not use this file except in compliance with the 
 *	   License. You may obtain a copy of the License at 
 *	   http://www.firebirdsql.org/index.php?op=doc&id=idpl
 *
 *	   Software distributed under the License is distributed on 
 *	   an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either 
 *	   express or implied. See the License for the specific 
 *	   language governing rights and limitations under the License.
 * 
 *	Copyright (c) 2002, 2005 Carlos Guzman Alvarez
 *	All Rights Reserved.
 */

using System;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Globalization;
using System.Text;

using FirebirdSql.Data.Common;

namespace FirebirdSql.Data.Firebird
{
	/// <include file='Doc/en_EN/FbCommandBuilder.xml' path='doc/class[@name="FbCommandBuilder"]/overview/*'/>
	public sealed class FbCommandBuilder : Component
	{
		#region Fields

		private FbDataAdapter dataAdapter;
		private FbCommand insertCommand;
		private FbCommand updateCommand;
		private FbCommand deleteCommand;
		private DataTable schemaTable;
		private string sqlInsert;
		private string sqlUpdate;
		private string sqlDelete;
		private string separator;
		private string whereClausule1;
		private string whereClausule2;
		private string setClausule;
		private string quotePrefix;
		private string quoteSuffix;
		private string tableName;
		private string timestampColumnName;
		private bool usingCoalesce;
		private bool disposed;
		private bool hasPrimaryKey;

		private FbCommandBuilderBehavior commandBuilderBehavior;
		private FbRowUpdatingEventHandler adapterHandler;

		#endregion

		#region Properties

		/// <include file='Doc/en_EN/FbCommandBuilder.xml' path='doc/class[@name="FbCommandBuilder"]/property[@name="DataAdapter"]/*'/>
#if	(!NETCF)
		[DefaultValue(null)]
#endif
		public FbDataAdapter DataAdapter
		{
			get { return this.dataAdapter; }
			set
			{
				this.dataAdapter = value;

				// Registers the CommandBuilder	as a listener for RowUpdating events that are 
				// generated by the	FbDataAdapter specified	in this	property.
				if (this.dataAdapter != null)
				{
					this.adapterHandler = new FbRowUpdatingEventHandler(this.RowUpdatingHandler);
					this.dataAdapter.RowUpdating += this.adapterHandler;
				}
			}
		}

		/// <include file='Doc/en_EN/FbCommandBuilder.xml' path='doc/class[@name="FbCommandBuilder"]/property[@name="QuotePrefix"]/*'/>
		public string QuotePrefix
		{
			get { return this.quotePrefix; }
			set
			{
				if (this.insertCommand != null ||
					this.updateCommand != null ||
					this.deleteCommand != null)
				{
					throw new InvalidOperationException("This property cannot be changed after an insert, update, or delete command has been generated.");
				}

				this.quotePrefix = value;
			}
		}

		/// <include file='Doc/en_EN/FbCommandBuilder.xml' path='doc/class[@name="FbCommandBuilder"]/property[@name="QuoteSuffix"]/*'/>
		public string QuoteSuffix
		{
			get { return this.quoteSuffix; }
			set
			{
				if (this.insertCommand != null ||
					this.updateCommand != null ||
					this.deleteCommand != null)
				{
					throw new InvalidOperationException("This property cannot be changed after an insert, update, or delete command has been generated.");
				}

				this.quoteSuffix = value;
			}
		}

		/// <include file='Doc/en_EN/FbCommandBuilder.xml' path='doc/class[@name="FbCommandBuilder"]/property[@name="CommandBuilderBehavior"]/*'/>
#if	(!NETCF)
		[DefaultValue(FbCommandBuilderBehavior.Default)]
#endif
		public FbCommandBuilderBehavior CommandBuilderBehavior
		{
			get { return this.commandBuilderBehavior; }
			set { this.commandBuilderBehavior = value; }
		}

		/// <include file='Doc/en_EN/FbCommandBuilder.xml' path='doc/class[@name="FbCommandBuilder"]/property[@name="timestampColumnName"]/*'/>
#if	(!NETCF)
		[DefaultValue("")]
#endif
		public string TimestampColumnName
		{
			get { return this.timestampColumnName; }
			set
			{
				if (value == null)
				{
					value = String.Empty;
				}
				this.timestampColumnName = value;
			}
		}


		#endregion

		#region Internal properties

		internal FbCommand SelectCommand
		{
			get
			{
				if (this.dataAdapter.SelectCommand != null)
				{
					return this.dataAdapter.SelectCommand;
				}

				return null;
			}
		}

		#endregion

		#region Constructors

		/// <include file='Doc/en_EN/FbCommandBuilder.xml' path='doc/class[@name="FbCommandBuilder"]/constructor[@name="ctor"]/*'/>
		public FbCommandBuilder() : base()
		{
			this.sqlInsert		= "INSERT INTO {0} ({1}) VALUES ({2})";
			this.sqlUpdate		= "UPDATE {0} SET {1} WHERE ({2})";
			this.sqlDelete		= "DELETE FROM {0} WHERE ({1})";
			this.whereClausule1 = "(({0} IS NULL) OR ({0} = {1}))";
			this.whereClausule2 = "({0} = {1})";
			this.setClausule	= "{0} = {1}";
			this.separator		= ",";
			this.quotePrefix	= "\"";
			this.quoteSuffix	= "\"";
			this.timestampColumnName	= String.Empty;
			this.commandBuilderBehavior = FbCommandBuilderBehavior.Default;

			GC.SuppressFinalize(this);
		}

		/// <include file='Doc/en_EN/FbCommandBuilder.xml' path='doc/class[@name="FbCommandBuilder"]/property[@name="ctor(FbDataAdapter)"]/*'/>
		public FbCommandBuilder(FbDataAdapter adapter) : this()
		{
			this.DataAdapter = adapter;
		}

		#endregion

		#region IDisposable	Methods

		/// <include file='Doc/en_EN/FbCommandBuilder.xml' path='doc/class[@name="FbCommandBuilder"]/method[@name="Dispose(System.Boolean)"]/*'/>
		protected override void Dispose(bool disposing)
		{
			if (!this.disposed)
			{
				try
				{
					if (disposing)
					{
						// Clear generated commands
						this.RefreshSchema();

						// Clear FbDataAdapter handler if needed
						if (this.adapterHandler != null)
						{
							this.dataAdapter.RowUpdating -= adapterHandler;
						}

						// Clear fields
						this.sqlInsert		= null;
						this.sqlUpdate		= null;
						this.sqlDelete		= null;
						this.whereClausule1 = null;
						this.whereClausule2 = null;
						this.setClausule	= null;
						this.separator		= null;
						this.quotePrefix	= null;
						this.quoteSuffix	= null;
						this.dataAdapter	= null;
						this.timestampColumnName = null;
					}

					// release any unmanaged resources

					this.disposed = true;
				}
				finally
				{
					base.Dispose(disposing);
				}
			}
		}

		#endregion

		#region Static Methods

		/// <include file='Doc/en_EN/FbCommandBuilder.xml' path='doc/class[@name="FbCommandBuilder"]/method[@name="DeriveParameters(FbCommand)"]/*'/>
		public static void DeriveParameters(FbCommand command)
		{
			if (command.CommandType != CommandType.StoredProcedure)
			{
				throw new InvalidOperationException("The command text is not a valid stored procedure name.");
			}

			string spName		= command.CommandText.Trim();
			string quotePrefix	= "\"";
			string quoteSuffix	= "\"";

			if (spName.StartsWith(quotePrefix) && spName.EndsWith(quoteSuffix))
			{
				spName = spName.Substring(1, spName.Length - 2);
			}
			else
			{
				spName = spName.ToUpper(CultureInfo.CurrentCulture);
			}

			command.Parameters.Clear();

			DataView dataTypes = command.Connection.GetSchema("DataTypes").DefaultView;

			DataTable spSchema = command.Connection.GetSchema(
				"ProcedureParameters", new string[] { null, null, spName });

			int count = 1;
			foreach (DataRow row in spSchema.Rows)
			{
				dataTypes.RowFilter = String.Format(
					CultureInfo.CurrentCulture,
					"TypeName = '{0}'",
					row["PARAMETER_DATA_TYPE"]);

				FbParameter parameter = command.Parameters.Add(
					"@" + row["PARAMETER_NAME"].ToString().Trim(),
				 FbDbType.VarChar);

				parameter.FbDbType = (FbDbType)dataTypes[0]["ProviderDbType"];

				parameter.Direction = (ParameterDirection)row["PARAMETER_DIRECTION"];

				parameter.Size = Convert.ToInt32(row["PARAMETER_SIZE"], CultureInfo.InvariantCulture);

				if (parameter.FbDbType == FbDbType.Decimal ||
					parameter.FbDbType == FbDbType.Numeric)
				{
					if (row["NUMERIC_PRECISION"] != DBNull.Value)
					{
						parameter.Precision = Convert.ToByte(row["NUMERIC_PRECISION"], CultureInfo.InvariantCulture);
					}
					if (row["NUMERIC_SCALE"] != DBNull.Value)
					{
						parameter.Scale = Convert.ToByte(row["NUMERIC_SCALE"], CultureInfo.InvariantCulture);
					}
				}

				count++;
			}
		}

		#endregion

		#region Methods

		/// <include file='Doc/en_EN/FbCommandBuilder.xml' path='doc/class[@name="FbCommandBuilder"]/method[@name="GetInsertCommand"]/*'/>
		public FbCommand GetInsertCommand()
		{
			lock (this)
			{
				if (this.insertCommand == null)
				{
					this.BuildInsertCommand(null, null);
				}
			}

			return this.insertCommand;
		}

		/// <include file='Doc/en_EN/FbCommandBuilder.xml' path='doc/class[@name="FbCommandBuilder"]/method[@name="GetUpdateCommand"]/*'/>
		public FbCommand GetUpdateCommand()
		{
			lock (this)
			{
				if (this.updateCommand == null)
				{
					this.BuildUpdateCommand(null, null);
				}
			}

			return updateCommand;
		}

		/// <include file='Doc/en_EN/FbCommandBuilder.xml' path='doc/class[@name="FbCommandBuilder"]/method[@name="GetDeleteCommand"]/*'/>
		public FbCommand GetDeleteCommand()
		{
			lock (this)
			{
				if (this.deleteCommand == null)
				{
					this.BuildDeleteCommand(null, null);
				}
			}

			return this.deleteCommand;
		}

		/// <include file='Doc/en_EN/FbCommandBuilder.xml' path='doc/class[@name="FbCommandBuilder"]/method[@name="RefreshSchema"]/*'/>
		public void RefreshSchema()
		{
			lock (this)
			{
				// Clear DataAdapter commands if needed
				if (this.dataAdapter != null)
				{
					if (this.insertCommand == this.dataAdapter.InsertCommand)
					{
						this.dataAdapter.InsertCommand = null;
					}
					if (this.deleteCommand == this.dataAdapter.DeleteCommand)
					{
						this.dataAdapter.DeleteCommand = null;
					}
					if (this.updateCommand == this.dataAdapter.UpdateCommand)
					{
						this.dataAdapter.UpdateCommand = null;
					}
				}

				// Clear commands
				if (this.insertCommand != null)
				{
					this.insertCommand.Dispose();
				}
				if (this.updateCommand != null)
				{
					this.updateCommand.Dispose();
				}
				if (this.deleteCommand != null)
				{
					this.deleteCommand.Dispose();
				}
				if (this.schemaTable != null)
				{
					this.schemaTable.Dispose();
				}
				this.insertCommand = null;
				this.updateCommand = null;
				this.deleteCommand = null;
				this.schemaTable = null;
			}
		}

		#endregion

		#region Command	Building Methods

		private FbCommand BuildInsertCommand(DataRow row, DataTableMapping tableMapping)
		{
			if (this.commandBuilderBehavior == FbCommandBuilderBehavior.KeyAndTimestampFields &&
				(this.timestampColumnName == null ||
				this.timestampColumnName.Length == 0))
			{
				throw new InvalidOperationException();
			}

			StringBuilder sql		= new StringBuilder();
			StringBuilder fields	= new StringBuilder();
			StringBuilder values	= new StringBuilder();

			this.BuildSchemaTable();

			this.CreateCommand(ref this.insertCommand);

			int i = 0;
			foreach (DataRow schemaRow in schemaTable.Rows)
			{
				if (this.IsUpdatable(schemaRow, row, tableMapping))
				{
					if (fields.Length > 0)
					{
						fields.Append(this.separator);
					}
					if (values.Length > 0)
					{
						values.Append(this.separator);
					}

					fields.Append(this.GetQuotedIdentifier(schemaRow["BaseColumnName"]));

					FbParameter parameter = this.CreateParameter(schemaRow, i, false);

					values.Append(parameter.ParameterName);

					if (row != null && tableMapping != null)
					{
						DataColumn column = this.GetDataColumn(
							schemaRow["BaseColumnName"].ToString(),
							tableMapping,
							row);

						if (column != null)
						{
							parameter.Value = row[column];
						}
					}

					i++;

					this.insertCommand.Parameters.Add(parameter);
				}
			}

			sql.AppendFormat(
				CultureInfo.CurrentCulture,
				this.sqlInsert,
				this.GetQuotedIdentifier(tableName),
				fields.ToString(),
				values.ToString());

			this.insertCommand.CommandText = sql.ToString();

			return this.insertCommand;
		}

		private FbCommand BuildUpdateCommand(DataRow row, DataTableMapping tableMapping)
		{
			if (this.commandBuilderBehavior == FbCommandBuilderBehavior.KeyAndTimestampFields &&
				(this.timestampColumnName == null ||
				this.timestampColumnName.Length == 0))
			{
				throw new InvalidOperationException();
			}

			StringBuilder sql	= new StringBuilder();
			StringBuilder sets	= new StringBuilder();
			StringBuilder where = new StringBuilder();

			this.BuildSchemaTable();

			if (!this.hasPrimaryKey)
			{
				throw new InvalidOperationException("Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not return any key column information.");
			}

			this.CreateCommand(ref this.updateCommand);

			int i = 0;
			foreach (DataRow schemaRow in schemaTable.Rows)
			{
				if (this.IsUpdatable(schemaRow, row, tableMapping))
				{
					if (sets.Length > 0)
					{
						sets.Append(separator);
					}

					FbParameter parameter = this.CreateParameter(schemaRow, i, false);

					// Update SET clausule
					sets.AppendFormat(
						CultureInfo.CurrentCulture,
						setClausule,
						this.GetQuotedIdentifier(schemaRow["BaseColumnName"]),
						parameter.ParameterName);

					if (row != null && tableMapping != null)
					{
						DataColumn column = this.GetDataColumn(
							schemaRow["BaseColumnName"].ToString(),
							tableMapping,
							row);

						if (column != null)
						{
							parameter.Value = row[column];
						}
					}

					i++;

					this.updateCommand.Parameters.Add(parameter);
				}
			}

			// Build where clausule
			foreach (DataRow schemaRow in schemaTable.Rows)
			{
				if (this.IncludedInWhereClause(schemaRow))
				{
					if (where.Length > 0)
					{
						where.Append(" AND ");
					}

					string quotedId = this.GetQuotedIdentifier(schemaRow["BaseColumnName"]);

					// Create parameters for this field
					FbParameter parameter = this.CreateParameter(schemaRow, i, true);

					// Add where clausule for this field
					if ((bool)schemaRow["IsKey"])
					{
						where.AppendFormat(
							CultureInfo.CurrentCulture,
							whereClausule2,
							quotedId,
							parameter.ParameterName);
					}
					else
					{
						if (this.usingCoalesce)
						{
							string typeName = TypeHelper.GetDataTypeName((DbDataType)parameter.FbDbType);

							switch (typeName)
							{
								case "VARCHAR":
								case "CHAR":
									typeName = String.Format(
										CultureInfo.CurrentCulture,
										"{0}({1})",
										typeName,
										schemaRow["ColumnSize"]);
									break;

								case "DECIMAL":
								case "NUMERIC":
									typeName = String.Format(
										CultureInfo.CurrentCulture,
										"{0}({1},{2})",
										typeName,
										schemaRow["NumericPrecision"],
										schemaRow["NumericScale"]);
									break;
							}

							where.AppendFormat(
								CultureInfo.CurrentCulture,
								whereClausule1,
								quotedId,
								parameter.ParameterName,
								typeName);
						}
						else
						{
							where.AppendFormat(
								CultureInfo.CurrentCulture,
								whereClausule1,
								quotedId,
								parameter.ParameterName);
						}
					}

					if (row != null && tableMapping != null)
					{
						DataColumn column = this.GetDataColumn(
							schemaRow["BaseColumnName"].ToString(),
							tableMapping,
							row);

						if (column != null)
						{
							parameter.Value = row[column, DataRowVersion.Original];
						}
					}

					this.updateCommand.Parameters.Add(parameter);

					i++;
				}
			}

			sql.AppendFormat(
				CultureInfo.CurrentCulture,
				this.sqlUpdate,
				this.GetQuotedIdentifier(tableName),
				sets.ToString(),
				where.ToString());

			this.updateCommand.CommandText = sql.ToString();

			return this.updateCommand;
		}

		private FbCommand BuildDeleteCommand(DataRow row, DataTableMapping tableMapping)
		{
			if (this.commandBuilderBehavior == FbCommandBuilderBehavior.KeyAndTimestampFields &&
				(this.timestampColumnName == null ||
				this.timestampColumnName.Length == 0))
			{
				throw new InvalidOperationException();
			}

			StringBuilder sql = new StringBuilder();
			StringBuilder where = new StringBuilder();

			this.BuildSchemaTable();

			if (!this.hasPrimaryKey)
			{
				throw new InvalidOperationException("Dynamic SQL generation for the DeleteCommand is not supported against a SelectCommand that does not return any key column information.");
			}

			this.CreateCommand(ref this.deleteCommand);

			// Build where clausule
			int i = 0;
			foreach (DataRow schemaRow in schemaTable.Rows)
			{
				if (this.IncludedInWhereClause(schemaRow))
				{
					if (where.Length > 0)
					{
						where.Append(" AND ");
					}

					string quotedId = this.GetQuotedIdentifier(schemaRow["BaseColumnName"]);

					// Create parameters for this field
					FbParameter parameter = this.CreateParameter(schemaRow, i, true);

					if ((bool)schemaRow["IsKey"])
					{
						where.AppendFormat(
							CultureInfo.CurrentCulture,
							whereClausule2,
							quotedId,
							parameter.ParameterName);
					}
					else
					{
						if (this.usingCoalesce)
						{
							string typeName = TypeHelper.GetDataTypeName((DbDataType)parameter.FbDbType);

							switch (typeName)
							{
								case "VARCHAR":
								case "CHAR":
									typeName = String.Format(
										CultureInfo.CurrentCulture,
										"{0}({1})",
										typeName,
										schemaRow["ColumnSize"]);
									break;

								case "DECIMAL":
								case "NUMERIC":
									typeName = String.Format(
										CultureInfo.CurrentCulture,
										"{0}({1},{2})",
										typeName,
										schemaRow["NumericPrecision"],
										schemaRow["NumericScale"]);
									break;
							}

							where.AppendFormat(
								CultureInfo.CurrentCulture,
								whereClausule1,
								quotedId,
								parameter.ParameterName,
								typeName);
						}
						else
						{
							where.AppendFormat(
								CultureInfo.CurrentCulture,
								whereClausule1,
								quotedId,
								parameter.ParameterName);
						}
					}

					if (row != null && tableMapping != null)
					{
						DataColumn column = this.GetDataColumn(
							schemaRow["BaseColumnName"].ToString(),
							tableMapping,
							row);

						if (column != null)
						{
							parameter.Value = row[column, DataRowVersion.Original];
						}
					}

					this.deleteCommand.Parameters.Add(parameter);

					i++;
				}
			}

			sql.AppendFormat(
				CultureInfo.CurrentCulture,
				this.sqlDelete,
				this.GetQuotedIdentifier(tableName),
				where.ToString());

			this.deleteCommand.CommandText = sql.ToString();

			return this.deleteCommand;
		}

		private bool IsUpdatable(
			DataRow schemaRow, DataRow row, DataTableMapping tableMapping)
		{
			if (row != null && tableMapping != null)
			{
				DataColumn column = this.GetDataColumn(
					schemaRow["BaseColumnName"].ToString(),
					tableMapping,
					row);

				if (column != null)
				{
					if (column.Expression != null &&
						column.Expression.Length != 0)
					{
						return false;
					}
					if (column.ReadOnly)
					{
						return false;
					}
				}
			}

			if ((bool)schemaRow["IsExpression"])
			{
				return false;
			}
			if ((bool)schemaRow["IsAutoIncrement"])
			{
				return false;
			}
			if ((bool)schemaRow["IsRowVersion"])
			{
				return false;
			}
			if ((bool)schemaRow["IsReadOnly"])
			{
				return false;
			}

			return true;
		}

		private bool IncludedInWhereClause(DataRow schemaRow)
		{
			if (!(bool)schemaRow["IsKey"] &&
				(this.commandBuilderBehavior == FbCommandBuilderBehavior.KeyFields ||
				this.commandBuilderBehavior == FbCommandBuilderBehavior.KeyAndTimestampFields))
			{
				if (this.timestampColumnName != schemaRow["BaseColumnName"].ToString())
				{
					return false;
				}
			}

			FbDbType dbType = (FbDbType)schemaRow["ProviderType"];

			if (dbType == FbDbType.Array || dbType == FbDbType.Binary)
			{
				return false;
			}

			if ((bool)schemaRow["IsLong"])
			{
				return false;
			}

			return true;
		}

		private void BuildSchemaTable()
		{
			bool mustClose = false;

			if (this.SelectCommand == null)
			{
				throw new InvalidOperationException("The DataAdapter.SelectCommand property needs to be initialized.");
			}
			if (this.SelectCommand.Connection == null)
			{
				throw new InvalidOperationException("The DataAdapter.SelectCommand.Connection property needs to be initialized.");
			}

			if (this.schemaTable == null)
			{
				if (this.SelectCommand.Connection.State == ConnectionState.Closed)
				{
					mustClose = true;
					this.SelectCommand.Connection.Open();
				}

				try
				{
					FbCommand schemaCmd = (FbCommand)((ICloneable)this.SelectCommand).Clone();

					FbDataReader reader = schemaCmd.ExecuteReader(CommandBehavior.SchemaOnly);

					this.schemaTable = reader.GetSchemaTable();

					reader.Close();

					schemaCmd.Dispose();
					schemaCmd = null;

					this.CheckSchemaTable();

					this.UpdateFormats();
				}
				catch
				{
					throw;
				}
				finally
				{
					if (mustClose)
					{
						this.SelectCommand.Connection.Close();
					}
				}
			}
		}

		private void CheckSchemaTable()
		{
			this.tableName = String.Empty;
			this.hasPrimaryKey = false;

			foreach (DataRow schemaRow in schemaTable.Rows)
			{
				if (this.tableName.Length == 0)
				{
					this.tableName = (string)schemaRow["BaseTableName"];
				}
				if (this.tableName != (string)schemaRow["BaseTableName"] &&
					!(bool)schemaRow["IsExpression"])
				{
					throw new InvalidOperationException("Dynamic SQL generation is not supported against multiple base tables.");
				}
				if ((bool)schemaRow["IsKey"] || (bool)schemaRow["IsUnique"])
				{
					this.hasPrimaryKey = true;
				}
			}
		}

		private string GetQuotedIdentifier(object identifier)
		{
			string suffix = this.quoteSuffix != null ? this.quoteSuffix : String.Empty;
			string prefix = this.quotePrefix != null ? this.quotePrefix : String.Empty;

			return prefix + identifier.ToString() + suffix;
		}

		private void CreateCommand(ref FbCommand command)
		{
			if (command == null)
			{
				command = this.SelectCommand.Connection.CreateCommand();
			}

			command.Transaction = this.SelectCommand.Transaction;
			command.CommandType = CommandType.Text;
			command.UpdatedRowSource = UpdateRowSource.None;
			command.Parameters.Clear();
		}

		private FbParameter CreateParameter(
			DataRow schemaRow, int index, bool isWhereParameter)
		{
			string pname = String.Format(CultureInfo.CurrentCulture, "@p{0}", index + 1);
			FbParameter parameter = new FbParameter(pname, (FbDbType)schemaRow["ProviderType"]);

			parameter.Size = Convert.ToInt32(schemaRow["ColumnSize"], CultureInfo.InvariantCulture);
			if (schemaRow["NumericPrecision"] != DBNull.Value)
			{
				parameter.Precision = Convert.ToByte(schemaRow["NumericPrecision"], CultureInfo.InvariantCulture);
			}
			if (schemaRow["NumericScale"] != DBNull.Value)
			{
				parameter.Scale = Convert.ToByte(schemaRow["NumericScale"], CultureInfo.InvariantCulture);
			}
			parameter.SourceColumn = schemaRow["BaseColumnName"].ToString();
			parameter.IsNullable = Convert.ToBoolean(schemaRow["AllowDbNull"], CultureInfo.InvariantCulture);

			if (isWhereParameter)
			{
				parameter.SourceVersion = DataRowVersion.Original;
			}
			else
			{
				parameter.SourceVersion = DataRowVersion.Current;
			}

			return parameter;
		}

		private DataColumn GetDataColumn(
			string columnName, DataTableMapping tableMapping, DataRow row)
		{
			DataColumn dataColumn = null;

			// Get the DataColumnMapping that matches
			// the given column	name
			DataColumnMapping columnMapping = tableMapping.GetColumnMappingBySchemaAction(
				columnName, this.dataAdapter.MissingMappingAction);

			if (columnMapping != null)
			{
				// Get the DataColumn for the given	column name
				dataColumn = columnMapping.GetDataColumnBySchemaAction(
					row.Table,
					null,
					this.dataAdapter.MissingSchemaAction);
			}

			return dataColumn;
		}

		#endregion

		#region Event Handler Methods

		private void RowUpdatingHandler(object sender, FbRowUpdatingEventArgs e)
		{
			if (e.Status != UpdateStatus.Continue)
			{
				return;
			}

			if (e.Command != null)
			{
				// Check that we can really	build a	new	command.
				// If the command passed in	the	FbRowUpdatingEventArgs
				// is different	than the one existent in the CommabdBuilder
				// we can't	build a	new	command

				FbCommand command = null;

				switch (e.StatementType)
				{
					case StatementType.Insert:
						command = this.insertCommand;
						break;

					case StatementType.Delete:
						command = this.deleteCommand;
						break;

					case StatementType.Update:
						command = this.updateCommand;
						break;
				}

				if (command != e.Command)
				{
					return;
				}
			}

			try
			{
				switch (e.StatementType)
				{
					case StatementType.Insert:
						e.Command = this.BuildInsertCommand(
								e.Row,
								e.TableMapping);
						break;

					case StatementType.Update:
						e.Command = this.BuildUpdateCommand(
								e.Row,
								e.TableMapping);
						break;

					case StatementType.Delete:
						e.Command = this.BuildDeleteCommand(
								e.Row,
								e.TableMapping);
						break;
				}
			}
			catch (Exception exception)
			{
				e.Errors = exception;
				e.Status = UpdateStatus.ErrorsOccurred;
			}
		}

		#endregion

		#region Private	Methods

		private void UpdateFormats()
		{
			this.usingCoalesce = false;

			if (this.SelectCommand.Connection != null)
			{
				string version = this.SelectCommand.Connection.ServerVersion;

				if (version.IndexOf("Firebird 1.5") > 0)
				{
					this.whereClausule1 = "(({0} IS NULL AND COALESCE({1}, CAST(NULL AS {2})) IS NULL) OR ({0} = {1}))";
					this.usingCoalesce = true;
				}
			}
		}

		#endregion
	}
}
